﻿using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices.Protocols;
using System.Security.Cryptography.X509Certificates;
using System.Net;

namespace Sio.Mdm.Connector
{
    public class Connector : ConnectorBase, IConnector
    {
        private LdapConnection _ldapConnection;

        #region Methods
        #region Public
        
        #region Constructor
        public Connector()
        {
                        
        }

        public Connector(eLdapDirectoryType directoryType, string directoryBase)
        {
            _directoryType = directoryType;
            _directoryBase = directoryBase;
        }
        #endregion

        #region Connect
        public override void Connect(string server, int port, string userName, string password, bool useSSL)
        {
            _isConnected = false;
            _isUsingSSL = useSSL;
            _server = server;
            _port = port;
            
            if (!server.Contains(":")) server = string.Format("{0}:{1}", server, port.ToString());                                            

            NetworkCredential credential;
            credential = new NetworkCredential(userName, password, "");
            _ldapConnection = new LdapConnection(server);
            _ldapConnection.AuthType = AuthType.Basic;
            _ldapConnection.Credential = credential;
            _ldapConnection.SessionOptions.ProtocolVersion = 3;

            if (useSSL) {
                _ldapConnection.SessionOptions.SecureSocketLayer = true;
                _ldapConnection.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(ServerCallback);            
            }

            _ldapConnection.Bind();
            
            _isConnected = true;
        }                
        #endregion

        #region Exists
        public override bool Exists(string value, eLdapSearchField searchField, eLdapSearchScope searchScope, string baseDN, eLdapSearchObject searchObject)
        {
            if (String.IsNullOrEmpty(baseDN)) baseDN = directoryBase;

            SearchScope scope = SearchScope.OneLevel;
            if (searchScope == eLdapSearchScope.subtree) scope = SearchScope.Subtree;

            string criteria = "";
            if (searchObject == eLdapSearchObject.user) criteria = GetUserSearchCriteria(searchField, value);
            if (searchObject == eLdapSearchObject.group) criteria = GetGroupSearchCriteria(searchField, value);

            SearchRequest searchRequest = new SearchRequest(baseDN, criteria, scope, null);
            SearchResponse searchResponse = (SearchResponse)_ldapConnection.SendRequest(searchRequest);

            if (searchResponse.Entries.Count == 0)
                return false;
            else
                _lastDN = searchResponse.Entries[0].DistinguishedName;
            return true;
        }
        #endregion

        #region Load
        public ldapObject Load(string DN)
        {
            ldapObject ldapObj = new ldapObject(DN);
            string baseDN = GetBaseDN(DN);
            string baseCN = GetBaseCN(DN);

            SearchScope scope = SearchScope.OneLevel;
            string criteria = GetUserSearchCriteria(eLdapSearchField.CN, baseCN);

            SearchRequest searchRequest = new SearchRequest(baseDN, criteria, scope, null);
            SearchResponse searchResponse = (SearchResponse)_ldapConnection.SendRequest(searchRequest);

            if (searchResponse.Entries.Count == 1) {
                SearchResultAttributeCollection attributes = searchResponse.Entries[0].Attributes;                

                foreach (DirectoryAttribute attribute in attributes.Values)
                {
                    string name = attribute.Name;

                    for(int i=0; i<attribute.Count;i++)
                    {
                        ldapObj.Add(name, attribute[i]);
                    }                    
                }
            }
            else 
            { 
                throw new Exception(String.Format("ldapObject with DN={0} not found",DN));
            }

            return ldapObj;
        }

        public List<ldapObject> LoadGroups(string baseDN) 
        {
            List<ldapObject> list = new List<ldapObject>();
            SearchScope scope = SearchScope.OneLevel;
            string criteria = GetGroupSearchCriteria(eLdapSearchField.All,"");

            SearchRequest searchRequest = new SearchRequest(baseDN, criteria, scope, null);
            SearchResponse searchResponse = (SearchResponse)_ldapConnection.SendRequest(searchRequest);

            foreach (SearchResultEntry entry in searchResponse.Entries)
            {
                SearchResultAttributeCollection attributes = entry.Attributes;
                ldapObject ldapObj = new ldapObject(entry.DistinguishedName);                

                foreach (DirectoryAttribute attribute in attributes.Values)
                {
                    string name = attribute.Name;
                    
                    for (int i = 0; i < attribute.Count; i++)
                    {
                        ldapObj.Add(name, attribute[i]);
                    }
                }
                list.Add(ldapObj);
            }
            if (list.Count == 0) {
                if (!Exists(baseDN, eLdapSearchField.DN, eLdapSearchScope.subtree, "", eLdapSearchObject.group))
                {
                    throw new Exception(String.Format("Object with DN={0} not found.", baseDN));
                }
            }

            return list;
        }
        #endregion

        #region Add Update Delete (Atributess)
        public void Add(ldapObject data)
        {
            PrepareObjectData(data);

            DirectoryAttribute[] attributes = FillAtributes(data);

            _ldapConnection.SendRequest(new AddRequest(data.DN, attributes));
        }
        
        public void Update(ldapObject data)
        {
            PrepareObjectData(data);

            //string dn = "CN=Časar Šelož389,OU=Učenci,OU=SARMA,DC=SARMA,DC=local";
            //ModifyRequest modRequest = new ModifyRequest(dn, DirectoryAttributeOperation.Replace, "displayName", "Časar Šelož");            
            //_ldapConnection.SendRequest(modRequest);

            DirectoryAttributeModification[] modifications = new DirectoryAttributeModification[data.Count()];
            
            int i = 0;
            foreach (KeyValuePair<string, List<object>> kvp in data)
            {
                string attributeName = kvp.Key;
                List<object> attributeList = (List<object>)kvp.Value;

                modifications[i] = new DirectoryAttributeModification();                
                modifications[i].Name = attributeName;
                modifications[i].Operation = DirectoryAttributeOperation.Replace;
                
                for (int j=0;j<attributeList.Count;j++)
                {
                    if (attributeList[j].GetType().ToString() == "System.String")
                    {
                        modifications[i].Add(Convert.ToString(attributeList[j]));
                    }
                    else if (attributeList[j].GetType().ToString() == "System.Byte[]")
                    {
                        modifications[i].Add((Byte[])attributeList[j]);
                    }                                        
                }

                i++;                             
            }

            _ldapConnection.SendRequest(new ModifyRequest(data.DN,modifications));
        }

        public void Delete(string dn)
        {            
            DeleteRequest delRequest = new DeleteRequest(dn);
            DeleteResponse delResponse = (DeleteResponse)_ldapConnection.SendRequest(delRequest);

            if (delResponse.ErrorMessage != null)
            {
                throw new Exception(String.Format("Delete for DN={0} failed!! Error: {1}", dn, delResponse.ErrorMessage));
            }
        }
        #endregion

        #region Add / Remove from group
        public void AddToGroup(string groupDN, string userDN)
        {
            DirectoryAttributeModification addMod = new DirectoryAttributeModification();
            addMod.Name = "member";
            addMod.Add(userDN);
            addMod.Operation = DirectoryAttributeOperation.Add;

            ModifyRequest modRequest = new ModifyRequest(groupDN, addMod);
            _ldapConnection.SendRequest(modRequest);
        }

        public void RemoveFromGroup(string groupDN, string userDN)
        {
            DirectoryAttributeModification addMod = new DirectoryAttributeModification();
            addMod.Name = "member";
            addMod.Add(userDN);
            addMod.Operation = DirectoryAttributeOperation.Delete;

            ModifyRequest modRequest = new ModifyRequest(groupDN, addMod);
            _ldapConnection.SendRequest(modRequest);
        }

        #endregion
        
        #region RenameCN
        public void RenameCN(string currentDN, string newCN)
        {
            try
            {
                if (!newCN.ToUpper().StartsWith("CN"))
                {
                    newCN = "cn=" + newCN;
                }
                
                ModifyDNRequest modDnRequest = new ModifyDNRequest(currentDN, GetBaseDN(currentDN), newCN);
                
                ModifyDNResponse modDnResponse = (ModifyDNResponse)_ldapConnection.SendRequest(modDnRequest);
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Error while CN renaming for DN={0}. Error description: ",currentDN, ex.Message + " " + ex.InnerException));      
            }
        }
        #endregion
        
        #endregion

        #region Private
        private DirectoryAttribute[] FillAtributes(ldapObject data)
        {
            DirectoryAttribute[] attributes = new DirectoryAttribute[data.Count()+2];            
            attributes[0] = new DirectoryAttribute("objectclass",data.objectClass);
            attributes[1] = new DirectoryAttribute("cn", data.CN);

            int i=2;
            foreach (KeyValuePair<string, List<object>> kvp in data)
            {
                string attributeName = kvp.Key;
                List<object> attributeList = (List<object>)kvp.Value;

                attributes[i] = new DirectoryAttribute(attributeName, attributeList.ToArray());
                i++;
            }

            return attributes;
        }        

        private static bool ServerCallback(LdapConnection connection, X509Certificate certificate)
        {
            X509Certificate2 newCert = new X509Certificate2(certificate);
            LdapDirectoryIdentifier id = (LdapDirectoryIdentifier)connection.Directory;

            string sName = id.Servers[0];
            return true;
        }

        #endregion

        #endregion

    }
}
